<template>
	<component :is="componentListInfo" :itemList="itemList"></component>
</template>

<script setup lang="tsx" name="CFormItemList">
import { ref, useSlots, inject } from 'vue'
import { FormItemProps } from '../../interface'
import { InfoFilled } from '@element-plus/icons-vue'
import CformItemList from './index.vue'
import { isNullOrUnDef, isArray as isArrayFun } from '@/utils/is'
import { deepClone } from '@/utils/util'
import AddItemBtn from './components/AddItemBtn.vue'
import DeleteItemBtn from './components/DeleteItemBtn.vue'
import { PROVIDE_TOKEN, CFormProvide } from '@/components/CForm/index.vue'
const prevent = inject<CFormProvide>(PROVIDE_TOKEN)
const formData = prevent?.formData

// 默认值
const props = withDefaults(
	defineProps<{
		itemList: FormItemProps[] // 配置项目列表
		model: Record<string, any> // itemList 对应的数据
		labelSuffix?: string // 表单域标签的后缀
		pPropPath?: string // 父 prop路径
		slotPrefix?: string // 插槽前缀
	}>(),
	{}
)

// 包装 isShow 方法，实现类似监听器，让逻辑只在数据改变触发
function wrapIsShowFun(isShowFun: Function) {
	let oldVal: any = null
	return function (item: FormItemProps, data: Record<string, any>, prop: string | number) {
		// 调用 isShow 方法
		const newVal = isShowFun({ item, data, prop, formData: formData?.value })
		if (oldVal !== newVal) {
			oldVal = newVal
			/* 数据改变触发逻辑 */
			if (newVal) {
				// 显示
				onItemShow(item, data, prop)
			} else {
				// 隐藏
				onItemHide(item, data, prop)
			}
		}
		return newVal
	}
}

// itemProp 初始化处理，并进行基本格式校验
function itemPropInitHandle(item: Partial<FormItemProps>): FormItemProps {
	const { isShow, compType, prop, itemInfo } = item

	let _isShow
	if (isShow !== undefined) {
		if (typeof isShow === 'function') {
			const wrapFun = wrapIsShowFun(isShow)
			_isShow = (item: FormItemProps, data: Record<string, any>, prop: string | number) => wrapFun(item, data, prop)
		} else {
			_isShow = () => isShow
		}
	} else {
		_isShow = () => true
	}

	// 如果是二维数组，需要递归
	/* 处理默认属性 */
	if (compType === 'array') {
		// 数组
		if (itemInfo) {
			if (isArrayFun(itemInfo)) {
				throw new Error(`元数据结构异常：${prop} 是 array 类型，itemInfo 必须是对象`)
			} else {
				// itemInfo 表示数组节点的信息
				const arrayItemInfo = itemInfo
				const arrayItemType = arrayItemInfo.compType
				if (arrayItemType === 'object') {
					/* 不需要这个组件处理的属性 */
					// 对象
				} else {
					/* 需要这个组件中处理的属性 */
					// 普通属性
					// 数组
					// prop 用父级的（数组每项都是同一个插槽），这个需要当前 CFormItemList 处理
					item.itemInfo = itemPropInitHandle({ prop, ...itemInfo })
				}
			}
		} else {
			throw new Error(`元数据结构异常：${prop} 是 array 类型，itemInfo 不能为空`)
		}
	}

	// 合并对象
	return {
		_isShow,
		span: 24,
		isArray: false,
		isHideWipeData: true,
		...item
	} as FormItemProps
}

const itemList = ref<FormItemProps[]>(
	props.itemList.map(item => {
		const { prop, itemInfo, compType, compDefault } = item
		// 是否是数组元素
		const data = props.model
		// 处理空值问题
		// 赋默认值，避免异常
		const value = data[prop]

		/* 添加 data 的默认值，因为当前 CFormItemList 只处理第一层就可以了，深层次默认值不用管 */
		if (isNullOrUnDef(value)) {
			const defaultVal = deepClone(compDefault) ?? null
			if (compType === 'array') {
				data[prop] = defaultVal ?? []
			} else if (compType === 'object') {
				// 对象
				if (!(isArrayFun(itemInfo) && itemInfo.length)) {
					// 不是非空数组
					throw new Error(`元数据结构异常：${prop} 是 object 类型，itemInfo 必须是非空数组`)
				}
				data[prop] = defaultVal ?? {}
			} else {
				data[prop] = defaultVal
			}
		}

		return itemPropInitHandle(item)
	})
)

// isShow 触发的 item 显示，只有 isShow 字段才会调用
function onItemShow(item: FormItemProps, data: Record<string, any>, prop: string | number) {
	// 显示时取值优先级： formData > 默认值 > null
	const value = data[prop]
	const defaultVal = item.compDefault ?? null
	data[prop] = isNullOrUnDef(value) ? deepClone(defaultVal) : value
}
// isShow 触发的 item 隐藏，只有 isShow 字段才会调用
function onItemHide(item: FormItemProps, data: Record<string, any>, prop: string | number) {
	const { isHideWipeData } = item
	// 隐藏置空数据
	if (isHideWipeData) {
		data[prop] = null
	}
}

const slots = useSlots()

// 创建插槽
function createSlot(slotName: string, item: Partial<FormItemProps>, data: Record<string, any>, prop: string | number) {
	const slot = slots['prop_' + slotName]
	return slot && <>{slot?.({ item, data, prop })}</>
}

// 创建 formItemList，意味着进入下一个 CformItemList 进行数据处理
function formItemList(
	itemList: FormItemProps[],
	data: Record<string, any>,
	prop: string | number,
	pPropPath: string,
	slotPrefix: string
) {
	// default: ({ item: cItem, data: cData, prop: cProp }: { item: FormItemProps; data: Record<string, any>; prop: string }) => {
	// 				return <>{slots.default?.({ item: cItem, data: cData, prop: cProp })}</>
	// 			}
	return (
		<CformItemList
			model={data[prop]}
			itemList={itemList}
			labelSuffix={props.labelSuffix}
			pPropPath={pPropPath}
			slotPrefix={slotPrefix}
		>
			{{
				...slots
			}}
		</CformItemList>
	)
}
// 普通 col，所有表单项都会被包裹
function colWrap(item: FormItemProps, childrenNode: any) {
	return (
		<>
			{item.isNewLine && <div class="new-line"></div>}
			<el-col span={item.span}>{childrenNode}</el-col>
		</>
	)
}
// 包含显示隐藏控制的 col
function colWrap_isShow(item: FormItemProps, data: Record<string, any>, prop: string | number, childrenNode: any) {
	return item._isShow!(item, data, prop) ? <>{colWrap(item, childrenNode)}</> : null
}

// 列表项包装
function arrayItemWrap(contentNodes: any, rightNodes: any, left?: string) {
	return (
		<el-card class="box-card">
			<div class="m-flex">
				<div class="m-content">{contentNodes}</div>
				<div class="m-right" style={{ marginLeft: left }}>
					{rightNodes}
				</div>
			</div>
			{/* <el-divider border-style="dashed" /> */}
		</el-card>
	)
}

// 创建交互表单项（叶节点）
function itemFun(
	item: Partial<FormItemProps>,
	data: Record<string, any>,
	prop: string | number,
	propPath: string,
	slotName: string
) {
	return (
		<el-form-item {...item} prop={propPath}>
			{{
				label:
					item.label === undefined
						? null
						: () => {
								return (
									<span>
										{item.label}
										{item.alert! && (
											<el-tooltip effect="dark" content={item.alert} placement="top">
												<el-icon>
													<InfoFilled />
												</el-icon>
											</el-tooltip>
										)}
										{props.labelSuffix}
									</span>
								)
						  },
				default: () => {
					return <>{createSlot(slotName, item, data, prop) ?? slots.default?.({ item, data, prop })}</>
				}
			}}
		</el-form-item>
	)
}

// 元数据项处理
function itemHandleFun(
	item: FormItemProps,
	data: Record<string, any>,
	prop: string | number,
	propPath: string,
	slotName: string,
	isArrayItem?: boolean
): any {
	const { compType, itemInfo } = item
	// 数组类型
	if (compType === 'array') {
		// 节点配置
		if (itemInfo) {
			/* 存在子节点 */
			const list = data[prop] as any[]
			if (!isArrayFun(itemInfo)) {
				// itemInfo 表示数组节点的信息
				const arrayItemInfo = itemInfo
				const arrayItemType = arrayItemInfo.compType
				const arrayItemChildren = arrayItemInfo.itemInfo
				if (arrayItemType === 'object') {
					/* 关联下一个组件 */
					if (isArrayFun(arrayItemChildren) && arrayItemChildren.length) {
						// 对象
						const result: any = []
						if (list && list.length) {
							list.forEach((_, index) => {
								result.push(
									colWrap(
										arrayItemInfo as FormItemProps,
										arrayItemWrap(
											<>{formItemList(arrayItemChildren, list, index, `${propPath}.${index}`, `${slotName}_item`)}</>,
											<>
												<AddItemBtn arrayItemProp={arrayItemInfo} list={list} index={index}></AddItemBtn>
												<DeleteItemBtn list={list} index={index}></DeleteItemBtn>
											</>
										)
									)
								)
							})
						}
						return colWrap_isShow(
							item,
							data,
							prop,
							<>
								<el-form-item {...item} prop={propPath}>
									<el-row gutter={10} style={{ width: '100%' }}>
										{createSlot(slotName, item, data, prop) ?? result}
									</el-row>
									<AddItemBtn arrayItemProp={itemInfo} list={list}></AddItemBtn>
								</el-form-item>
							</>
						)
					}
				} else {
					/* 需要这个组件中处理的属性 */
					// 普通属性
					// 数组
					// prop 用父级的（数组每项都是同一个插槽），这个需要当前 CFormItemList 处理
					// 子项是非对象
					const result: any = []
					if (list && list.length) {
						list.forEach((_, index) => {
							result.push(itemHandleFun(itemInfo as FormItemProps, list, index, `${propPath}.${index}`, `${slotName}_item`, true))
						})
					}
					return colWrap_isShow(
						item,
						data,
						prop,
						<>
							<el-form-item {...item} prop={propPath}>
								<el-row gutter={10} style={{ width: '100%' }}>
									{createSlot(slotName, item, data, prop) ?? result}
								</el-row>
								<AddItemBtn arrayItemProp={itemInfo} list={list}></AddItemBtn>
								{isArrayFun(data) && <DeleteItemBtn list={data} index={+prop}></DeleteItemBtn>}
							</el-form-item>
						</>
					)
				}
			}
		}
	}
	// 对象类型
	else if (compType === 'object') {
		/* 关联下一个组件 */
		if (isArrayFun(itemInfo) && itemInfo.length) {
			return colWrap_isShow(item, data, prop, formItemList(itemInfo, data, prop, `${propPath}`, slotName))
		}
	}
	// 基本类型
	else {
		let cNode
		if (isArrayItem) {
			// 数组项
			cNode = arrayItemWrap(
				<>{itemFun(item, data, prop, propPath, slotName)}</>,
				<>
					<AddItemBtn arrayItemProp={item} list={data} index={+prop}></AddItemBtn>
					<DeleteItemBtn list={data} index={+prop}></DeleteItemBtn>
				</>,
				'10px'
			)
		} else {
			cNode = itemFun(item, data, prop, propPath, slotName)
		}
		return colWrap_isShow(item, data, prop, cNode)
	}
}

// 组件信息
function componentListInfo({ itemList }: { itemList: FormItemProps[] }) {
	return (
		<el-row gutter={10} style={{ width: '100%' }}>
			{itemList.map(item =>
				itemHandleFun(
					item,
					props.model,
					item.prop,
					props.pPropPath ? `${props.pPropPath}.${item.prop}` : item.prop,
					props.slotPrefix ? `${props.slotPrefix}_${item.prop}` : item.prop
				)
			)}
		</el-row>
	)
}
</script>
<style scoped lang="scss">
:deep(.m-flex) {
	display: flex;
	flex-direction: row;
	width: 100%;
	.m-content {
		flex: 1;

		// border: 1px solid blue;
	}
	.m-right {
		flex: 0 0 80px;
		text-align: center;

		// border: 1px solid blue;
	}
}
:deep(.el-form-item .el-form-item) {
	// 覆盖默认样式
	margin-bottom: 18px;
}
:deep(.box-card) {
	margin-bottom: 10px;
	.el-card__body {
		padding: 10px 5px 0;
	}
}

// 用来换行的 dom
:deep(.new-line) {
	width: 100%;
}
</style>
